<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>带语音识别的RTC推流 - LiveTalking数字人交互平台</title>
    <!-- 引入Bootstrap 5.3.0 -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- 引入jQuery 3.6.0 -->
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <!-- 引入Bootstrap JS -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <!-- 引入SRS SDK (WHIP推流器) -->
    <script src="https://ossrs.net/players/srsRtcPublisher-2.0.148.js"></script>
    <!-- 引入mpegts.js -->
    <script src="https://cdn.jsdelivr.net/npm/mpegts.js@1.7.3/dist/mpegts.js"></script>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
            background-size: 400% 400%;
            animation: gradientBG 15s ease infinite;
            color: #fff;
            line-height: 1.6;
            padding: 20px;
            min-height: 100vh;
        }
        
        @keyframes gradientBG {
            0% { background-position: 0% 50%; }
            50% { background-position: 100% 50%; }
            100% { background-position: 0% 50%; }
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: rgba(0, 5, 30, 0.85);
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
            overflow: hidden;
            border: 1px solid rgba(100, 150, 255, 0.3);
        }
        
        header {
            background: linear-gradient(to right, #0f0c29, #302b63, #24243e);
            padding: 20px 30px;
            text-align: center;
            border-bottom: 1px solid #4a5fc2;
        }
        
        h1 {
            font-size: 2.2rem;
            margin-bottom: 10px;
            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
            background: linear-gradient(to right, #4facfe, #00f2fe);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
        }
        
        .subtitle {
            color: #a0c8ff;
            font-size: 1.1rem;
            max-width: 800px;
            margin: 0 auto;
        }
        
        .content {
            padding: 25px;
            display: grid;
            grid-template-columns: 1fr;
            gap: 25px;
        }
        
        @media (min-width: 900px) {
            .content {
                grid-template-columns: 1fr 1fr;
            }
        }
        
        .section {
            background: rgba(10, 20, 50, 0.6);
            border-radius: 12px;
            padding: 20px;
            border: 1px solid rgba(100, 150, 255, 0.2);
        }
        
        .section-title {
            color: #4facfe;
            font-size: 1.4rem;
            margin-bottom: 15px;
            padding-bottom: 10px;
            border-bottom: 1px solid rgba(100, 150, 255, 0.3);
        }
        
        .video-container {
            position: relative;
            border-radius: 12px;
            overflow: hidden;
            background: #000;
            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4);
            aspect-ratio: 16/9;
            margin-bottom: 20px;
        }
        
        #video-preview {
            width: 100%;
            height: 100%;
            background: #000;
        }
        
        .controls {
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            align-items: center;
            margin-bottom: 20px;
        }
        
        .stun-config {
            flex: 1;
            min-width: 200px;
        }
        
        .stun-config label {
            display: block;
            margin-bottom: 8px;
            color: #a0c8ff;
            font-weight: 500;
        }
        
        #stun-server {
            width: 100%;
            padding: 10px 12px;
            border-radius: 8px;
            border: 1px solid #4a5fc2;
            background: rgba(10, 20, 50, 0.7);
            color: #fff;
            font-size: 1rem;
            transition: border-color 0.3s;
        }
        
        #stun-server:focus {
            outline: none;
            border-color: #4facfe;
            box-shadow: 0 0 0 2px rgba(79, 172, 254, 0.3);
        }
        
        .button-group {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }
        
        .control-btn {
            background: linear-gradient(to right, #4facfe, #00f2fe);
            color: #0f0c29;
            border: none;
            border-radius: 8px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s ease;
            padding: 8px 16px;
            font-size: 1.1rem;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
        }
        
        .control-btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
            filter: brightness(1.05);
        }
        
        .control-btn:active {
            transform: translateY(0);
        }
        
        .control-btn[disabled] {
            background: #555;
            cursor: not-allowed;
            transform: none;
            box-shadow: none;
        }
        
        .status-indicator {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            padding: 8px 15px;
            border-radius: 20px;
            background: rgba(0, 0, 0, 0.3);
            font-size: 0.95rem;
            margin-bottom: 20px;
        }
        
        .status-dot {
            width: 12px;
            height: 12px;
            border-radius: 50%;
            background: #ff4757;
        }
        
        .status-dot.connected {
            background: #2ed573;
            box-shadow: 0 0 8px #2ed573;
        }
        
        .status-dot.connecting {
            background: #ffa502;
            animation: pulse 1.5s infinite;
        }
        
        @keyframes pulse {
            0% { box-shadow: 0 0 0 0 rgba(255, 165, 2, 0.7); }
            70% { box-shadow: 0 0 0 10px rgba(255, 165, 2, 0); }
            100% { box-shadow: 0 0 0 0 rgba(255, 165, 2, 0); }
        }
        
        .connection-info {
            background: rgba(0, 0, 0, 0.2);
            border-radius: 8px;
            padding: 12px;
            margin-top: 15px;
            font-size: 0.9rem;
        }
        
        .info-item {
            display: flex;
            justify-content: space-between;
            margin-bottom: 6px;
        }
        
        .info-label {
            color: #a0c8ff;
            font-weight: 500;
        }
        
        .transcript-container {
            background: rgba(0, 5, 30, 0.7);
            border-radius: 10px;
            padding: 15px;
            margin-top: 15px;
            border: 1px solid rgba(100, 150, 255, 0.2);
            min-height: 150px;
            max-height: 200px;
            overflow-y: auto;
        }
        
        .transcript-text {
            line-height: 1.6;
        }
        
        .transcript-interim {
            color: #a0c8ff;
            font-style: italic;
        }
        
        .transcript-final {
            color: #4facfe;
            margin-bottom: 10px;
        }
        
        .error-message {
            background: #ff4757;
            color: white;
            padding: 15px;
            border-radius: 8px;
            margin-top: 15px;
            display: none;
            animation: fadeIn 0.3s;
        }
        
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(-10px); }
            to { opacity: 1; transform: translateY(0); }
        }
        
        footer {
            text-align: center;
            padding: 20px;
            color: #a0c8ff;
            font-size: 0.9rem;
            border-top: 1px solid rgba(100, 150, 255, 0.2);
            margin-top: 10px;
        }
        
        @media (max-width: 768px) {
            .controls {
                flex-direction: column;
            }
            
            .stun-config {
                width: 100%;
            }
            
            h1 {
                font-size: 1.8rem;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>带语音识别的RTC推流</h1>
            <p class="subtitle">LiveTalking数字人交互平台 - 实时音视频推流与语音识别</p>
        </header>
        
        <div class="content">
            <div class="section">
                <h2 class="section-title">视频推流</h2>
                <div class="video-container">
                    <video id="video-preview" autoplay playsinline muted></video>
                </div>
                
                <div class="controls">
                    <div class="stun-config">
                        <label for="stun-server">STUN服务器</label>
                        <select id="stun-server">
                            <option value="">自动选择 (推荐)</option>
                            <option value="stun:stun.l.google.com:19302">Google STUN 服务器</option>
                            <option value="stun:global.stun.twilio.com:3478?transport=udp">Twilio STUN 服务器</option>
                            <option value="stun:stun.services.mozilla.com">Mozilla STUN 服务器</option>
                        </select>
                    </div>
                    <div class="button-group">
                        <button id="start-btn" class="control-btn">开始推流</button>
                        <button id="stop-btn" class="control-btn" disabled>停止推流</button>
                    </div>
                </div>
                
                <div class="status-indicator">
                    <span class="status-dot" id="connection-status"></span>
                    <span id="status-text">等待推流...</span>
                </div>
                
                <div class="connection-info" id="connection-info" style="display:none;">
                    <div class="info-item">
                        <span class="info-label">服务器地址:</span>
                        <span id="server-address">http://<span id="hostname">localhost</span>:1985/rtc/v1/whip/</span>
                    </div>
                    <div class="info-item">
                        <span class="info-label">流名称:</span>
                        <span>live/livestream</span>
                    </div>
                    <div class="info-item">
                        <span class="info-label">推流状态:</span>
                        <span id="publish-state">未推流</span>
                    </div>
                </div>
                
                <div class="error-message" id="error-message">
                    推流失败: 无法建立WebRTC连接，请检查网络或STUN服务器设置
                </div>
            </div>
            
            <div class="section">
                <h2 class="section-title">语音识别</h2>
                <div class="controls">
                    <div class="button-group">
                        <button id="start-speech-btn" class="control-btn">开始识别</button>
                        <button id="stop-speech-btn" class="control-btn" disabled>停止识别</button>
                    </div>
                </div>
                
                <div class="status-indicator">
                    <span class="status-dot" id="speech-status"></span>
                    <span id="speech-status-text">语音识别未启动</span>
                </div>
                
                <div class="transcript-container">
                    <div class="transcript-text" id="transcript-text">
                        <div class="transcript-final">语音识别结果将显示在这里...</div>
                    </div>
                </div>
                
                <div class="connection-info" style="margin-top: 15px;">
                    <div class="info-item">
                        <span class="info-label">识别语言:</span>
                        <span>中文 (简体)</span>
                    </div>
                    <div class="info-item">
                        <span class="info-label">识别状态:</span>
                        <span id="recognition-state">未启动</span>
                    </div>
                </div>
            </div>
        </div>
        
        <footer>
            LiveTalking 数字人交互平台 &copy; 2023 | 基于 WebRTC 和 SRS 实时流媒体技术
        </footer>
    </div>

    <script>
        $(document).ready(function() {
            // 全局变量
            let publisher = null;
            let publishState = 'disconnected'; // disconnected, connecting, connected, error
            let mediaStream = null;
            
            // 语音识别相关变量
            let recognition = null;
            let isRecognizing = false;
            let finalTranscript = '';
            let interimTranscript = '';
            
            // 设置当前主机名
            const hostname = window.location.hostname || 'localhost';
            $('#hostname').text(hostname);
            
            // 初始化
            function init() {
                updatePublishState('disconnected');
                updateStatus('等待推流...', 'disconnected');
                $('#video-preview').hide();
                $('#connection-info').hide();
                $('#error-message').hide();
                $('#stop-btn').prop('disabled', true);
                
                // 初始化语音识别状态
                updateSpeechStatus('inactive');
                updateSpeechStatusText('语音识别未启动');
                $('#stop-speech-btn').prop('disabled', true);
                
                // 检查浏览器是否支持语音识别
                if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
                    updateSpeechStatusText('浏览器不支持语音识别功能');
                    $('#start-speech-btn').prop('disabled', true);
                } else {
                    // 初始化语音识别对象
                    const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
                    recognition = new SpeechRecognition();
                    recognition.continuous = true;
                    recognition.interimResults = true;
                    recognition.lang = 'zh-CN';
                    
                    recognition.onstart = function() {
                        isRecognizing = true;
                        updateSpeechStatus('active');
                        updateSpeechStatusText('语音识别进行中...');
                        $('#start-speech-btn').prop('disabled', true);
                        $('#stop-speech-btn').prop('disabled', false);
                        updateRecognitionState('识别中');
                    };
                    
                    recognition.onresult = function(event) {
                        interimTranscript = '';
                        for (let i = event.resultIndex; i < event.results.length; ++i) {
                            if (event.results[i].isFinal) {
                                finalTranscript += event.results[i][0].transcript;
                                addFinalTranscript(event.results[i][0].transcript);
                            } else {
                                interimTranscript += event.results[i][0].transcript;
                            }
                        }
                        updateTranscript();
                    };
                    
                    recognition.onerror = function(event) {
                        console.error('Speech recognition error', event.error);
                        updateSpeechStatus('error');
                        updateSpeechStatusText('语音识别错误: ' + event.error);
                        stopSpeechRecognition();
                    };
                    
                    recognition.onend = function() {
                        isRecognizing = false;
                        updateSpeechStatus('inactive');
                        updateSpeechStatusText('语音识别已停止');
                        $('#start-speech-btn').prop('disabled', false);
                        $('#stop-speech-btn').prop('disabled', true);
                        updateRecognitionState('已停止');
                    };
                }
            }
            
            // 更新推流状态
            function updatePublishState(state) {
                publishState = state;
                $('#publish-state').text(
                    state === 'connected' ? '推流中' :
                    state === 'connecting' ? '连接中...' :
                    state === 'error' ? '推流错误' : '未推流'
                );
                
                // 更新状态指示器
                const statusDot = $('#connection-status');
                statusDot.removeClass('connected connecting');
                if (state === 'connected') {
                    statusDot.addClass('connected');
                } else if (state === 'connecting') {
                    statusDot.addClass('connecting');
                }
            }
            
            // 更新状态文本
            function updateStatus(text, type) {
                $('#status-text').text(text);
                
                if (type === 'error') {
                    $('#error-message').text(text).show();
                    setTimeout(() => {
                        $('#error-message').fadeOut(500);
                    }, 5000);
                } else {
                    $('#error-message').hide();
                }
            }
            
            // 更新语音识别状态
            function updateSpeechStatus(status) {
                const statusDot = $('#speech-status');
                statusDot.removeClass('connected connecting active error');
                
                if (status === 'active') {
                    statusDot.addClass('connecting');
                } else if (status === 'error') {
                    statusDot.addClass('error');
                } else {
                    statusDot.addClass('connected');
                }
            }
            
            function updateSpeechStatusText(text) {
                $('#speech-status-text').text(text);
            }
            
            function updateRecognitionState(text) {
                $('#recognition-state').text(text);
            }
            
            // 开始推流
            async function startPublish() {
                if (publishState === 'connecting' || publishState === 'connected') {
                    return;
                }
                
                try {
                    // 获取媒体流
                    mediaStream = await navigator.mediaDevices.getUserMedia({ 
                        video: true, 
                        audio: true 
                    });
                    
                    // 显示预览
                    $('#video-preview').show().attr('srcObject', mediaStream);
                    
                    updatePublishState('connecting');
                    updateStatus('正在建立WebRTC推流连接...', 'info');
                    $('#start-btn').prop('disabled', true).text('连接中...');
                    
                    // 获取STUN服务器配置
                    const stunServer = $('#stun-server').val();
                    const iceServers = [];
                    
                    if (stunServer) {
                        iceServers.push({ urls: stunServer });
                    } else {
                        // 默认STUN服务器
                        iceServers.push({ 
                            urls: [
                                'stun:stun.l.google.com:19302',
                                'stun:global.stun.twilio.com:3478?transport=udp'
                            ]
                        });
                    }
                    
                    // SRS WHIP 推流器配置
                    const url = `http://${hostname}:1985/rtc/v1/whip/?app=live&stream=livestream`;
                    const config = {
                        stream: mediaStream,
                        url: url,
                        iceServers: iceServers,
                        api: url,
                        schema: 'http://',
                        port: 1985,
                        app: 'live',
                        stream: 'livestream'
                    };
                    
                    // 创建推流器实例
                    publisher = new SrsRtcPublisherAsync();
                    
                    // 设置事件监听
                    publisher.onconnect = function() {
                        console.log('Publisher connected');
                        updatePublishState('connected');
                        updateStatus('WebRTC推流已建立', 'success');
                        $('#connection-info').show();
                        $('#start-btn').text('推流中').prop('disabled', true);
                        $('#stop-btn').prop('disabled', false);
                    };
                    
                    publisher.onpublish = function() {
                        console.log('Publisher started publishing');
                    };
                    
                    publisher.onerror = function(error) {
                        console.error('Publisher error:', error);
                        handlePublishError(error);
                    };
                    
                    // 开始推流
                    await publisher.publish(config);
                    console.log('Publisher started successfully');
                    
                } catch (error) {
                    console.error('Publisher initialization error:', error);
                    handlePublishError(error);
                }
            }
            
            // 停止推流
            function stopPublish() {
                if (publisher) {
                    publisher.close();
                    publisher = null;
                }
                
                if (mediaStream) {
                    mediaStream.getTracks().forEach(track => track.stop());
                    mediaStream = null;
                }
                
                $('#video-preview').hide().attr('srcObject', null);
                updatePublishState('disconnected');
                updateStatus('推流已停止', 'info');
                $('#start-btn').text('开始推流').prop('disabled', false);
                $('#stop-btn').prop('disabled', true);
                $('#connection-info').hide();
            }
            
            // 处理推流错误
            function handlePublishError(error) {
                updatePublishState('error');
                
                let errorMessage = 'WebRTC推流失败';
                if (error.message) {
                    errorMessage += ': ' + error.message;
                } else if (typeof error === 'string') {
                    errorMessage += ': ' + error;
                }
                
                updateStatus(errorMessage, 'error');
                $('#start-btn').prop('disabled', false).text('开始推流');
                $('#stop-btn').prop('disabled', true);
                
                // 显示错误消息
                $('#error-message').text(errorMessage).show();
            }
            
            // 开始语音识别
            function startSpeechRecognition() {
                if (!recognition) return;
                
                try {
                    recognition.start();
                } catch (error) {
                    console.error('Failed to start speech recognition:', error);
                    updateSpeechStatus('error');
                    updateSpeechStatusText('无法启动语音识别: ' + error.message);
                }
            }
            
            // 停止语音识别
            function stopSpeechRecognition() {
                if (!recognition || !isRecognizing) return;
                
                recognition.stop();
            }
            
            // 更新转录文本
            function updateTranscript() {
                const transcriptContainer = $('#transcript-text');
                transcriptContainer.empty();
                
                if (finalTranscript) {
                    transcriptContainer.append(`<div class="transcript-final">${finalTranscript}</div>`);
                }
                
                if (interimTranscript) {
                    transcriptContainer.append(`<div class="transcript-interim">${interimTranscript}</div>`);
                }
                
                // 滚动到底部
                transcriptContainer.scrollTop(transcriptContainer[0].scrollHeight);
            }
            
            // 添加最终转录文本
            function addFinalTranscript(text) {
                // 这里可以将识别结果发送到后端API
                console.log('Final transcript:', text);
            }
            
            // 唇形同步功能
            function lipSyncWithSpeech(text) {
                // 在实际应用中，这里会根据语音内容控制数字人的口型
                // 模拟唇形同步效果
                console.log('Lip syncing with speech:', text);
                
                // 可以通过CSS动画或Canvas动画实现口型变化
                // 这里只是一个示例
                const lipSyncIndicator = $('<div>').addClass('lip-sync-indicator').text('口型同步中...');
                $('.section-title').first().after(lipSyncIndicator);
                
                // 3秒后移除指示器
                setTimeout(() => {
                    lipSyncIndicator.remove();
                }, 3000);
            }
            
            // 增强语音识别结果处理
            function processSpeechResult(text) {
                addFinalTranscript(text);
                lipSyncWithSpeech(text);
            }
            
            // 事件绑定
            $('#start-btn').click(startPublish);
            $('#stop-btn').click(stopPublish);
            
            $('#start-speech-btn').click(startSpeechRecognition);
            $('#stop-speech-btn').click(stopSpeechRecognition);
            
            // 初始化
            init();
        });
    </script>
</body>
</html>
